package org.zhuzx.util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

/**
 * 通用工具类
 * @author	zhuzx	2016年10月10日
 */
@SuppressWarnings("unchecked")
public class GeneralUtil {
	
	/**
	 * 判断字符串是否为空
	 */
	public static boolean isBlank(String str) {
		return str == null || str.trim().length() == 0;
	}

	/**
	 * 判断字符串是否为非空
	 */
	public static boolean isNotBlank(String str) {
		return str != null && str.trim().length() > 0;
	}
	
	/**
	 * 判断对象是否为空，简单根据toString的结果判断，主要用于Object引用的字符串。<br>
	 * 注：若需要对其它类型的对象做内容判断，请使用isEmpty方法。
	 */
	public static boolean isBlank(Object obj) {
		return obj == null || obj.toString().trim().length() == 0;
	}

	/**
	 * 判断对象是否为非空，简单根据toString的结果判断，主要用于Object引用的字符串。<br>
	 * 注：若需要对其它类型的对象做内容判断，请使用isEmpty方法。
	 */
	public static boolean isNotBlank(Object obj) {
		return obj != null && obj.toString().trim().length() > 0;
	}

	/**
	 * 已迁移至isHaveAnyBlank()，语义更明确，并且传入String数组时不会与isBlank(Object obj)混淆。<br>
	 * 判断字符串列表中是否有任意一个为空
	 */
	@Deprecated
	public static boolean isBlank(String... strs) {
		return isHaveAnyBlank(strs);
	}

	/**
	 * 判断字符串列表中是否有任意一个为空
	 */
	public static boolean isHaveAnyBlank(String... strs) {
		for (String str : strs) {
			if (isBlank(str)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 判断字符串列表是否全部为空
	 */
	public static boolean isAllBlank(String... strs) {
		for (String str : strs) {
			if (isNotBlank(str)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 已迁移至isHaveAnyBlank()，语义更明确，并且传入String数组时不会与isBlank(Object obj)混淆。<br>
	 * 判断字符串列表中是否有任意一个为空
	 */
	@Deprecated
	public static boolean isBlank(Object... objs) {
		return isHaveAnyBlank(objs);
	}

	/**
	 * 判断对象列表中是否有任意一个为空
	 */
	public static boolean isHaveAnyBlank(Object... objs) {
		for (Object obj : objs) {
			if (isBlank(obj)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 判断对象列表是否全部为空
	 */
	public static boolean isAllBlank(Object... objs) {
		for (Object obj : objs) {
			if (isNotBlank(obj)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 判断集合是否为空
	 */
	public static boolean isEmpty(Collection<?> collection) {
		return collection == null || collection.size() == 0;
	}
	
	/**
	 * 判断数组是否为空
	 */
	public static <E> boolean isEmpty(E[] array) {
		return array == null || array.length == 0;
	}
	
	/**
	 * 判断Map是否为空
	 */
	public static boolean isEmpty(Map<?,?> map) {
		return map == null || map.size() == 0;
	}

	/**
	 * 判断是否为空内容对象，会根据具体类型判断是否有实际的数据内容，比如集合的元素数量、数组的长度等等。
	 */
	public static boolean isEmpty(Object obj) {
		if (obj == null) {
			return true;
		}
		if (obj instanceof String) {
			return isBlank((String) obj);
		} else if (obj instanceof Collection) {
			return isEmpty((Collection<?>) obj);
		} else if (obj instanceof Map) {
			return isEmpty((Map<?,?>) obj);
		} else if (obj.getClass().isArray()) {
			return Array.getLength(obj) == 0;
		} else {
			return isBlank(obj);
		}
	}

	/**
	 * 判断是否有任意一个对象为空或者空内容
	 */
	public static boolean isHaveAnyEmpty(Object... objs) {
		for (Object obj : objs) {
			if (isEmpty(obj)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 判断是否全部对象都为空内容
	 */
	public static boolean isAllEmpty(Object... objs) {
		for (Object obj : objs) {
			if (!isEmpty(obj)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 判断是否刚好只有一个对象有值（非null），全为null或者有两个以上非null都返回false。<br>
	 * 注意：不区分是否为空字符串或者空集合，只判断是否为null。
	 * 在接口入参多选一的场景中，用于验证是否传了多余参数以免取参时被干扰，直接用null判断是最严格的。
	 */
	public static boolean isExactlyHaveOneValue(Object... objs) {
		int count = 0;
		for (Object o : objs) {
			if (o != null) {
				if (++count > 1) {
					return false;
				}
			}
		}
		return count == 1;
	}

	/**
	 * 判断这些对象的有值无值情况是否一致，当全为空或者全不为空时才返回true。<br>
	 * 注意：会根据具体类型判断是否有实际的数据内容，而非仅仅判断是否为null。
	 */
	public static boolean isAllHaveOrNoneHaveValue(Object... objs) {
		int haveValueCount = 0;
		for (Object o : objs) {
			if (!isEmpty(o)) {
				haveValueCount++;
			}
		}
		return haveValueCount == 0 || haveValueCount == objs.length;
	}
	
	public static String getShorterString(String str1, String str2) {
		if (isBlank(str1, str2)) {
			throw new IllegalArgumentException("有参数为空时无法比较");
		}
		return str1.length() <= str2.length() ? str1 : str2;
	}
	
	/**
	 * 获取两者中的非空字符串，若target非空则取本身，为空则取replacement。
	 */
	public static String getNotBlankString(String target, String replacement) {
		return isNotBlank(target) ? target : replacement;
	}
	
	/**
	 * 获取两者中的非空字符串，若target非空则取本身，为空则取replacement。
	 */
	public static String getNotBlankString(Object target, String replacement) {
		return isNotBlank(target) ? target.toString() : replacement;
	}
	
	/**
	 * 获取两者中的非空字符串，若target非空则取本身，为空则取replacement。
	 */
	public static String getNotBlankString(Object target, Object replacement) {
		return isNotBlank(target) ? target.toString() :
				(replacement != null ? replacement.toString() : null);
	}

	/**
	 * 获取两者中的非空对象，若target非空则取本身，为空则取replacement。
	 */
	public static <T> T getNotNull(T target, T replacement) {
		return target != null ? target : replacement;
	}

	/**
	 * 将elements中的元素以connector拼接，得到一个字符串。
	 * 全部为空时不会返回null，而是返回空字符串。
	 * @param excludeBlank 是否排除空值（为null或者toString返回没有实际字符的空白串），
	 *                     若传入true，则不会拼接该元素（以及连接符），例如，对内容为<code>[1, null, 2]</code>的elements的拼接效果分别为：<br>
	 *                     <code>true ：1-2</code><br>
	 *                     <code>false：1-null-2</code>
	 */
	public static String concatString(String connector, Collection<?> elements, boolean excludeBlank) {
		StringBuilder sb = new StringBuilder();
		for (Object e : elements) {
			if (!excludeBlank || isNotBlank(e)) {//当非空，或者为空但调用者指定了不排除空值时，才拼接。
				sb.append(connector).append(e);
			}
		}
		return sb.length() > 0 ? sb.substring(connector.length()) : "";
	}

	/**
	 * 拼接为字符串，并将空值替换为指定字符（当传入了非null的替换符时）。<br>
	 * 不会缺失元素个数及连接符。
	 */
	public static String concatString(String connector, String replacementForBlank, Object... values) {
		List<Object> elements = Arrays.asList(values);
		if (replacementForBlank != null) {
			for (int i = 0; i < elements.size(); i++) {
				if (isBlank(elements.get(i))) {
					elements.set(i, replacementForBlank);
				}
			}
		}
		return concatString(connector, elements, false);
	}

	/**
	 * 连接非空的字符串，空值不显示为null也不产生多余的连接符。
	 * 全部为空时不会返回null，而是返回空字符串。
	 */
	public static String concatStringExcludeBlank(String connector, Object... values) {
		return concatString(connector, Arrays.asList(values), true);
	}

	/**
	 * 将两个字符串以连接符连接，并且会在连接处额外处理，保证不会有重复的连接符出现。<br>
	 * 即使两个字符串本身在两端自带了连接符，也不会在连接处产生重复的连接符。<br>
	 * 典型应用场景：url拼接。
	 */
	public static String concatStringWithUnduplicatedConnector(String str1, String connector, String str2) {
		Assert.notAllow(isBlank(str1, connector, str2), "必须传入有效字符");
		return MicroUtil.removeSuffix(str1, connector) + connector + MicroUtil.removePrefix(str2, connector);
	}

	/**
	 * 获取elements的字符串形式的拼接结果，注：String类型的元素可以用1.8的String.join。
	 */
	@Deprecated//迁移到concatString
	public static <E> String getCombinedString(Collection<E> elements, String separator) {
		return concatString(separator, elements, false);
	}
	
	/**
	 * 获取通用的标志位值：1为真，0为假。
	 */
	public static int getUnifiedFlagValue(boolean isTrue) {
		return isTrue ? UnifiedConstants.FLAG_AFFIRMATIVE : UnifiedConstants.FLAG_NEGATIVE;
	}
	
	/**
	 * 获取当前时间：yyyy-MM-dd HH:mm:ss
	 */
	public static String getDateTime() {
		return UnifiedConstants.getStandardDaf().format(new Date());
	}
	
	/**
	 * 获取当前日期：yyyy-MM-dd
	 */
	public static String getDate() {
		return UnifiedConstants.getDateDaf().format(new Date());
	}
	
	/**
	 * 获取前后第n天的日期
	 * @param daysOffset 指定天数偏移量，传0表示今天，传负数表示n天前。
	 * @return yyyy-MM-dd
	 */
	public static String getDate(int daysOffset) {
		Calendar cale = Calendar.getInstance();
		cale.set(
				cale.get(Calendar.YEAR),
				cale.get(Calendar.MONTH),
				cale.get(Calendar.DAY_OF_MONTH),
				0, 0, 0
		);
		cale.add(Calendar.DAY_OF_MONTH, daysOffset);
		return UnifiedConstants.getDateDaf().format(cale.getTime());
	}
	
	/**
	 * 获取前后第n天的头尾两个时间戳，即零点与最后一秒。<br>
	 * yyyy-MM-dd 00:00:00<br>
	 * yyyy-MM-dd 23:59:59
	 * @param daysOffset 指定天数偏移量，传0表示今天，传负数表示n天前。
	 */
	public static String[] getTimestamps(int daysOffset) {
		String date = getDate(daysOffset);
		return new String[] {date + " 00:00:00", date + " 23:59:59"};
	}
	
	/**
	 * 格式化为yyyy-MM-dd HH:mm:ss格式的字符串，目前支持：<br>
	 * Date、LocalDateTime、LocalDate
	 */
	public static String formatToStandardDateTime(Object time) {
		if (time == null) {
			return null;
		}
		return UnifiedConstants.getStandardDaf().format(parseToDate(time));
	}

	/**
	 * 将时间值转化为Date对象，目前支持：<br>
	 * Date、LocalDateTime、LocalDate
	 */
	public static Date parseToDate(Object time) {
		Date date;
		if (time instanceof Date) {
			date = (Date) time;
		} else if (time instanceof LocalDateTime) {
			date = Date.from(((LocalDateTime) time).atZone(ZoneId.systemDefault()).toInstant());
		} else if (time instanceof LocalDate) {
			date = Date.from(((LocalDate) time).atStartOfDay(ZoneId.systemDefault()).toInstant());
		} else {
			throw new IllegalArgumentException("暂不支持格式化此类型：" + time.getClass().getSimpleName());
		}
		return date;
	}
	
	/**
	 * 对某日期增加（减去）指定的时间量
	 * @param date 原始日期
	 * @param field 时间域，来自Calendar类中的常量，如Calendar.YEAR。
	 * @param value 增量值，若为负数则相减。
	 */
	public static void addTimes(Date date, int field, int value) {
		Calendar cale = Calendar.getInstance();
		cale.setTime(date);
		cale.add(field, value);
		date.setTime(cale.getTimeInMillis());
	}
	
	/**
	 * 获得增加（减去）指定时间量之后的一个新日期
	 * @param dateSrc 原始日期
	 * @param field 时间域，来自Calendar类中的常量，如Calendar.YEAR。
	 * @param value 增量值，若为负数则相减。
	 */
	public static Date getCalculatedDate(Date dateSrc, int field, int value) {
		Calendar cale = Calendar.getInstance();
		cale.setTime(dateSrc);
		cale.add(field, value);
		return new Date(cale.getTimeInMillis());
	}
	
	/**
	 * 获取上周的头尾两天的日期，yyyy-MM-dd格式。
	 * @param startBySunday 是否以星期日作为一周的开始
	 * @return 两个yyyy-MM-dd格式的字符串组成的数组
	 */
	public static String[] getTwoDateOfLastWeek(boolean startBySunday) {
		Calendar cale = Calendar.getInstance();
		int offset = cale.get(Calendar.DAY_OF_WEEK);
		if (!startBySunday) {
			offset = (offset==1) ? 7 : (offset-1);
		}
		cale.add(Calendar.DAY_OF_MONTH, -offset);
		String[] dates = new String[2];
		dates[1] = UnifiedConstants.getDateDaf().format(new Date(cale.getTimeInMillis()));
		cale.add(Calendar.DAY_OF_MONTH, -6);
		dates[0] = UnifiedConstants.getDateDaf().format(new Date(cale.getTimeInMillis()));
		return dates;
	}
	
	/**
	 * 获取上月的头尾两天的日期，yyyy-MM-dd格式。
	 * @return 两个yyyy-MM-dd格式的字符串组成的数组
	 */
	public static String[] getTwoDateOfLastMonth() {
		Calendar cale = Calendar.getInstance();
		int offset = cale.get(Calendar.DAY_OF_MONTH);
		cale.add(Calendar.DAY_OF_MONTH, -offset);
		String[] dates = new String[2];
		dates[1] = UnifiedConstants.getDateDaf().format(new Date(cale.getTimeInMillis()));
		cale.set(Calendar.DAY_OF_MONTH, 1);
		dates[0] = UnifiedConstants.getDateDaf().format(new Date(cale.getTimeInMillis()));
		return dates;
	}
	
	/**
	 * 获取指定两天之间所有的日期，yyyy-MM-dd格式，包含起始、结束本身。
	 * @param start 起始日期，yyyy-MM-dd格式。
	 * @param end 结束日期，yyyy-MM-dd格式。
	 * @return yyyy-MM-dd格式的字符串组成的数组
	 */
	public static String[] getAllDatesBetweenTwoDate(String start, String end) {
		if (start.equals(end)) {
			return new String[] {start};
		}
		if (start.compareTo(end) > 0) {
			throw new IllegalArgumentException("开始日期不能大于结束日期");
		}
		Date startDate;
		Date endDate;
		try {
			startDate = UnifiedConstants.getDateDaf().parse(start);
			endDate = UnifiedConstants.getDateDaf().parse(end);
		} catch (ParseException e) {
			throw new RuntimeException("日期格式必须为yyyy-MM-dd");
		}
		int days = (int) ((endDate.getTime() - startDate.getTime()) / UnifiedConstants.MILLIS_OF_DAY);
		String[] dates = new String[days+1];
		dates[0] = start;
		for (int i=1; i<days; i++) {
			addTimes(startDate, Calendar.DAY_OF_YEAR, 1);
			dates[i] = UnifiedConstants.getDateDaf().format(startDate);
		}
		dates[days] = end;
		return dates;
	}
	
	/**
	 * 计算两个时间相差的秒数，参数格式为yyyy-MM-dd HH:mm:ss。
	 */
	public static int calculateDifferenceSecondsOfTwoTime(String start, String end) {
		if (start.equals(end)) return 0;
		Date startDate = null;
		Date endDate = null;
		try {
			startDate = UnifiedConstants.getStandardDaf().parse(start);
			endDate = UnifiedConstants.getStandardDaf().parse(end);
		} catch (ParseException e) {
			throw new RuntimeException("日期格式必须为yyyy-MM-dd HH:mm:ss");
		}
		int secs = (int) ((endDate.getTime()-startDate.getTime()) / 1000);
		return secs;
	}
	
	/**
	 * 从数组中寻找匹配项，返回由索引组成的数组。
	 */
	@Deprecated//无效的泛型约束
	public static <E> int[] findEquals(E[] array, E e) {
		int[] founds = new int[array.length];
		int num = 0;
		for (int i=0; i<array.length; i++) {
			if (array[i].equals(e)) {
				founds[num] = i;
				num++;
			}
		}
		if (num < founds.length) {
			int[] sizeSuitable = new int[num];
			System.arraycopy(founds, 0, sizeSuitable, 0, num);
			founds = sizeSuitable;
		}
		return founds;
	}
	
	/**
	 * 从有序数组中寻找匹配项，返回由索引组成的数组。<br>
	 * 根据compareTo方法比较，结果为0则视为相等。
	 */
	@Deprecated//检查compareTo与equals方法的统一性。无效的泛型约束。
	@SuppressWarnings("unused")
	private static <E extends Comparable<? super E>> int[] findEqualsByComparable(E[] orderedArray, E e) {
		int[] founds = new int[orderedArray.length];
		int found = Arrays.binarySearch(orderedArray, e);
		int num = 0;
		while (found >= 0) {
			founds[num] = found;
			num++;
			found = Arrays.binarySearch(orderedArray, found+1, orderedArray.length, e);
		}
		if (num < founds.length) {
			int[] sizeSuitable = new int[num];
			System.arraycopy(founds, 0, sizeSuitable, 0, num);
			founds = sizeSuitable;
		}
		return founds;
	}
	
	/**
	 * 根据三个点的坐标计算中间点所在的夹角，即一条折线的夹角。
	 * @return 角度值
	 */
	public static double calculateAngle(double startXcoor, double startYcoor,
										double middleXcoor, double middleYcoor,
										double endXcoor, double endYcoor) {
		double a = Math.hypot(Math.abs(startXcoor-middleXcoor), Math.abs(startYcoor-middleYcoor));
		double b = Math.hypot(Math.abs(middleXcoor-endXcoor), Math.abs(middleYcoor-endYcoor));
		double c = Math.hypot(Math.abs(endXcoor-startXcoor), Math.abs(endYcoor-startYcoor));
		//cosC=(a²+b²-c²)/2ab
		return Math.acos((Math.pow(a,2) + Math.pow(b,2) - Math.pow(c,2)) / 2*a*b);
	}
	
	/**
	 * 创建一个Map比较器
	 * @param nullValueRep 当某键为空值时使用此替代值以控制该元素的位置
	 * @param keys 需要比较的键，可多个。
	 */
	@SuppressWarnings("unchecked")
	public static <K,V> Comparator<Map<K,V>> createMapComparator(
			final V nullValueRep, final K... keys) {
		Comparator<Map<K,V>> comparator = new Comparator<Map<K,V>>() {
			public int compare(Map<K,V> o1, Map<K,V> o2) {
				for (K aKey : keys) {
					V v1 = (o1.get(aKey)==null) ? nullValueRep : o1.get(aKey);
					V v2 = (o2.get(aKey)==null) ? nullValueRep : o2.get(aKey);
					if (v1 == null || v2 == null)
						throw new IllegalArgumentException("关键属性值" + aKey + "不能为空：\n" + o1 + "\n" + o2);
					int result = ((Comparable<V>) v1).compareTo(v2);
					if (result != 0)
						return result;
				}
				return 0;
			}
		};
		return comparator;
	}
	
	/**
	 * 创建一个用于比较数字键值的Map比较器
	 * @param keys 可多选，不可为空（编译器将自动生成参数new String[0]传入，执行后得到一个无效的比较器，比较结果永远为0）。
	 */
	@Deprecated//改成KV
	public static Comparator<Map<String,String>> createMapComparatorForNumber(final String... keys) {
		Comparator<Map<String,String>> comparator = new Comparator<Map<String,String>>() {
			public int compare(Map<String,String> o1, Map<String,String> o2) {
				for (String aKey : keys) {
					String v1 = o1.get(aKey);
					String v2 = o2.get(aKey);
					boolean isNullV1 = isBlank(v1);
					boolean isNullV2 = isBlank(v2);
					if (isNullV1 && isNullV2) {
						return 0;
					} else if (isNullV1) {
						return -1;
					} else if (isNullV2) {
						return 1;
					} else {
						int result = (new BigDecimal(v1)).compareTo(new BigDecimal(v2));
						if (result != 0) {
							return result;
						}
					}
				}
				return 0;
			}
		};
		return comparator;
	}
	
	/**
	 * 创建指定顺序的比较器，未列举的值则按升序排在其后。
	 * @param values
	 */
	//TODO CustomerRecoChangelogServiceImpl.getModifyDetail。干掉其中调用的WebUtil.getSortedString。顺便替换其中的分割方法。
	@SuppressWarnings("unchecked")
	public static <T> Comparator<T> createComparatorBySpecifiedOrder(T... values) {
		return null;
	}
	
	/**
	 * 加载classpath下的文件
	 */
	public static InputStream loadFileFromClasspath(String fileName) {
		return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
	}
	
	/**
	 * 读取classpath下的properties文件
	 * @param fileName 可以不加文件扩展名，但必须是.properties文件。
	 * @return 封装为Map返回
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static Map<String,String> loadPropsFileFromClasspath(String fileName) {
		if (!fileName.toLowerCase().endsWith("properties")) {
			fileName += ".properties";
		}
		Properties props = new Properties();
		try {
			InputStream in = loadFileFromClasspath(fileName);
			props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
			in.close();
		} catch (IOException e) {
			throw new RuntimeException("从classpath路径下读取文件" + fileName + "发生错误");
		}
		return new HashMap<String,String>((Map) props);
	}
	
	/**
	 * 加载指定的类所在包中的文件
	 */
	public static InputStream loadFileFromClassDirectory(Class<?> cla, String fileName) {
		String packagePath = cla.getPackage().getName().replace(".", File.separator) + File.separator;
		return cla.getClassLoader().getResourceAsStream(packagePath + fileName);
	}
	
	/**
	 * 读取指定的类所在包中的properties文件
	 * @param cla 目标文件位于该类所在目录
	 * @param fileName 可以不加文件扩展名，但必须是.properties文件。
	 * @return 封装为Map返回
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static Map<String,String> loadPropsFileFromClassDirectory(Class<?> cla, String fileName) {
		if (!fileName.toLowerCase().endsWith("properties")) {
			fileName += ".properties";
		}
		Properties props = new Properties();
		InputStream in = loadFileFromClassDirectory(cla, fileName);
		try {
			props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
			in.close();
		} catch (IOException e) {
			throw new RuntimeException("读取文件" + fileName + "发生错误");
		}
		return new HashMap<String,String>((Map) props);
	}
	
	/**
	 * 按行读取指定的类所在包中的文件的全部内容
	 * @return 文件中的每行文本对应列表中的一个字符串
	 */
	public static List<String> loadTextFileFromClassDirectory(Class<?> cla, String fileName) {
		InputStream is = loadFileFromClassDirectory(cla, fileName);
		List<String> lines = new ArrayList<String>();
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(is));
			String s;
			while ((s = reader.readLine()) != null) {
				lines.add(s);
			}
			is.close();
		} catch (IOException e) {
			throw new RuntimeException("读取文件" + fileName + "发生错误");
		}
		return lines;
	}
	
	/**
	 * 获取两个数组的合并数组
	 */
	public static <E> E[] getCombinedArray(E[] array1, E[] array2) {
		@SuppressWarnings("unchecked")
		E[] all = (E[]) Array.newInstance(array1.getClass().getComponentType(), array1.length + array2.length);
		System.arraycopy(array1, 0, all, 0, array1.length);
		System.arraycopy(array2, 0, all, array1.length, array2.length);
		return all;
	}
	
	/**
	 * 获取两个数组的合并数组
	 */
	public static byte[] getCombinedArray(byte[] array1, byte[] array2) {
		byte[] all = new byte[array1.length + array2.length];
		System.arraycopy(array1, 0, all, 0, array1.length);
		System.arraycopy(array2, 0, all, array1.length, array2.length);
		return all;
	}
	
	/**
	 * 获取两个数组的合并数组
	 */
	public static char[] getCombinedArray(char[] array1, char[] array2) {
		char[] all = new char[array1.length + array2.length];
		System.arraycopy(array1, 0, all, 0, array1.length);
		System.arraycopy(array2, 0, all, array1.length, array2.length);
		return all;
	}
	
	/**
	 * 获取输入流中的全部字节
	 * @param in 目标输入流，将被本方法读取后关闭。
	 */
	public static byte[] getBytes(InputStream in) {
		int size = 0;
		try {
			size = in.available();
		} catch (IOException e1) {}
		if (size <= 0) {
			size = UnifiedConstants.IO_BUFFER_SIZE;
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream(size);
		try {
			byte[] buffer = new byte[UnifiedConstants.IO_BUFFER_SIZE];
			int len;
			while ((len = in.read(buffer)) >= 0) {
				out.write(buffer, 0, len);
			}
			in.close();
			return out.toByteArray();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 向输出流写入指定字节序列
	 */
	@SuppressWarnings("unused")//似乎无此分段的必要
	private static void writeAllBytes(OutputStream out, byte[] bytes) {
		int off = 0;
		try {
			while (off < bytes.length) {
				int len = Math.min(bytes.length-off, UnifiedConstants.IO_BUFFER_SIZE);
				out.write(bytes, off, len);
				off += len;
			}
			out.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Thread.sleep(millis)
	 */
	public static void sleep(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		}
	}
	
	/**
	 * 特殊的equals方法，允许空对象参与判断，当两个对象都为空时视为相等。
	 */
	public static boolean nullableEquals(Object obj1, Object obj2) {
		if (obj1 == null || obj2 == null) {
			return obj1 == null && obj2 == null;
		} else {
			return obj1.equals(obj2);
		}
	}
	
	/**
	 * 根据具备映射关系的数据创建一个map
	 * @param mappedArray 每个子数组都有两个元素，第一个元素为键，第二个元素为值。
	 * 					（注：需要入参本身保证每个子数组有2个元素，并且键不为空，本方法不执行此检查。）
	 * @return LinkedHashMap
	 */
	public static Map<String,String> createMapping(String[][] mappedArray) {
		Map<String,String> map = new LinkedHashMap<>();
		for (String[] pair : mappedArray) {
			map.put(pair[0], pair[1]);
		}
		return map;
	}
	
	/**
	 * 拿到任意类型的数值对象所对应的double值。<br>
	 * 若传入null则返回0.0，若传入了不能转化的值则报错。
	 */
	public static double getDoubleValue(Object numObj) {
		if (numObj == null) {
			return 0.0;
		} else {
			if (numObj instanceof Double) {
				return (double) numObj;
			} else if (numObj instanceof BigDecimal) {
				return ((BigDecimal) numObj).doubleValue();
			} else if (numObj instanceof BigInteger) {
				return ((BigInteger) numObj).doubleValue();
			} else {
				return new BigDecimal(numObj.toString()).doubleValue();
			}
		}
	}
	
	/**
	 * 拿到任意类型的数值对象所对应的BigDecimal值。<br>
	 * 若传入null则返回{@code BigDecimal.ZERO}，若传入了不能转化的值则报错。
	 */
	public static BigDecimal getBigDecimalValue(Object numObj) {
		if (numObj == null) {
			return BigDecimal.ZERO;
		} else {
			if (numObj instanceof BigDecimal) {
				return (BigDecimal) numObj;
			} else {
				return new BigDecimal(numObj.toString());
			}
		}
	}
	
	/**
	 * 判断数值是否相等，不论参数双方是何种数字类型。若任意参数为null则不相等。
	 */
	public static boolean numberValueEquals(Object o1, Object o2) {
		if (isBlank(o1, o2)) {
			return false;
		}
		if (o1.getClass().equals(o2.getClass())) {
			if (o1 instanceof BigDecimal) {//BigDecimal的equals方法需要精度相等
				return ((BigDecimal) o1).compareTo((BigDecimal) o2) == 0;
			} else {
				return o1.equals(o2);
			}
		}
		BigDecimal bd1 = (o1 instanceof BigDecimal) ? (BigDecimal)o1 : new BigDecimal(o1.toString());
		BigDecimal bd2 = (o2 instanceof BigDecimal) ? (BigDecimal)o2 : new BigDecimal(o2.toString());
		return bd1.compareTo(bd2) == 0;
	}
	
	/**
	 * 执行算术运算：num1 operator num2，其中operator是操作符，比如：+ - * /<br>
	 * 注一：除法只取2位小数，四舍五入。因为除法必须得设置，否则有可能会报：Non-terminating decimal expansion。<br>
	 * 注二：为了提高业务实用性，对除法的分母为0的情况做特殊处理，返回运算结果为0而不是抛错。<br>
	 * 若业务上不能容忍分母为0的数据继续运行，则必须由调用者自己提前判断，抛出契合业务的异常提示。
	 * 如果由本方法抛出一个“除法的分母不能为0”的通用型异常文本就中断业务的话，既定位不到原因，也没有给用户一个明确的解释。
	 */
	public static BigDecimal arithmeticOperate(Object num1, char operator, Object num2) {
		BigDecimal v1 = getBigDecimalValue(num1);
		BigDecimal v2 = getBigDecimalValue(num2);
		switch (operator) {
		case '+':
			return v1.add(v2);
		case '-':
			return v1.subtract(v2);
		case '*':
			return v1.multiply(v2);
		case '/':
			if (v2.compareTo(BigDecimal.ZERO) == 0) {
				//分母为0时divide方法会报错“/ by zero”
				return BigDecimal.ZERO;
			}
			return v1.divide(v2, 2, RoundingMode.HALF_UP);
		default:
			throw new IllegalArgumentException("非法的操作符：" + operator);
		}
	}
	
	/**
	 * 通用的toString方法，用来处理一些特殊对象，防止其自带的toString方法返回不实用的文本格式。
	 * @return 一般情况下返回对象自己的toString结果，仅会对特殊对象做额外处理。
	 */
	public static String toString(Object o) {
		if (o == null) {
			return null;
		}
		String s = o.toString();
		if (o instanceof Timestamp) {
			if (s.endsWith(".0")) {
				//即使精度只到秒，Timestamp的toString方法也会缀加.0。比如：
				//new Timestamp((System.currentTimeMillis() / 1000) * 1000)
				s = s.substring(0, 19);
			}
		}
		if (o instanceof Double) {
			if (s.indexOf('E') > -1) {
				s = new BigDecimal(s).toPlainString();
			}
		} else if (o instanceof BigDecimal) {//BigInteger已测试，不会出现科学记数法格式。
			if (s.indexOf('E') > -1) {
				s = ((BigDecimal) o).toPlainString();
			}
		}
		if (o.getClass().isArray()) {
			if (o instanceof Object[])
				s = Arrays.deepToString((Object[]) o);
			else if (o instanceof byte[])
				s = Arrays.toString((byte[]) o);
			else if (o instanceof short[])
				s = Arrays.toString((short[]) o);
			else if (o instanceof int[])
				s = Arrays.toString((int[]) o);
			else if (o instanceof long[])
				s = Arrays.toString((long[]) o);
			else if (o instanceof float[])
				s = Arrays.toString((float[]) o);
			else if (o instanceof double[])
				s = Arrays.toString((double[]) o);
			else if (o instanceof char[])
				s = Arrays.toString((char[]) o);
			else if (o instanceof boolean[])
				s = Arrays.toString((boolean[]) o);
			else ExceptionUtil.neverHappen();
		}
		return s;
	}
	
	/**
	 * 比较两个同类型的对象，不论这两个对象的内容如何，即使equals为true甚至所有属性值相等，都会返回不相等的结果。<br>
	 * 注意：不会返回0，这个特性有别于Comparable.compareTo与Comparator.compare等通用的比较规则。<br>
	 * 唯一会返回0的情况就是o1和o2为同一个对象。此时无法比较出大小，只能返回0，为兼容一些特殊场景，允许如此调用。
	 */
	@Deprecated//内存地址会变化，暂无有效手段实现预期效果（能否从JVM取信息？），只能实现某些类型的不相等对象的比较。
	public static <T> int compareObjectWithoutEqual(T o1, T o2) {
		if (o1 == o2) {//唯一会返回0的情况
			return 0;
		}
		int result = 0;
		if (o1 instanceof Comparable) {
			@SuppressWarnings("unchecked")
			Comparable<T> c = (Comparable<T>) o1;
			result = c.compareTo(o2);
		}
		String str1 = o1.toString();
		String str2 = o2.toString();
		if (result == 0) {
			result = str1.compareTo(str2);
		}
		if (result == 0) {
			/*
			 * 大型容器对象会在此耗费大量的计算，在已经有toString比较的情况下，是否移除此项？
			 * 注：很多容器的hash策略都是迭代计算各个子对象的hashCode再做自己的hash运算，比如：
			 * HashMap使用AbstractMap.hashCode()，ArrayList使用AbstractList.hashCode()。
			 */
			result = Integer.compare(o1.hashCode(), o2.hashCode());
		}
		String errMsg = "无法比较以下两个对象：\n" + str1 + "\n" + str2;
		if (result == 0) {
			throw new RuntimeException(errMsg);
		}
		
		/* **************	暂时关闭以下两种	****************/
		
		if (result == 0) {
			//运行时变化？
			result = Integer.compare(System.identityHashCode(o1), System.identityHashCode(o2));
		}
		if (result == 0) {
			//TODO本处增加内存地址比较，并独立出一个compareObject方法，在内容实际equals的情况下返回0（排除Object.equals）。
			/* 内存地址会变化，包括默认的toString和hashCode都会变化，依赖地址进行比较是否可信？
			 * 初衷只是想用真实的内容比对替换掉compare方法作为TreeSet的唯一性依据，所以需要在compare中实现equals强度的比较效果。
			 * 实际上，不需要强求compare不等，内容完全一致的两个对象，在业务层面是该被TreeSet去重。
			 */
		}
		if (result == 0) {
			throw new RuntimeException(errMsg);
		}
		return result;
	}

	/**
	 * 移除该Map中value为空（为null或者空字符串）的键值对
	 */
	public static <K,V> void removeBlankEntry(Map<K,V> map) {
		map.entrySet().removeIf(entry -> isBlank(entry.getValue()));
	}

	/**
	 * 将字符串值解析为指定的原生类型，返回对应包装类的值对象。
	 */
	public static <T> T parsePrimitiveValue(String valueStr, Class<T> clazz) {
		if (valueStr == null || valueStr.isEmpty()) {
			return null;
		}
		String className = clazz.getSimpleName();
		if (clazz == byte.class || clazz == Byte.class) {
			return (T) Byte.valueOf(valueStr);
		} else if (clazz == short.class || clazz == Short.class) {
			return (T) Short.valueOf(valueStr);
		} else if (clazz == int.class || clazz == Integer.class) {
			return (T) Integer.valueOf(valueStr);
		} else if (clazz == long.class || clazz == Long.class) {
			return (T) Long.valueOf(valueStr);
		} else if (clazz == float.class || clazz == Float.class) {
			return (T) Float.valueOf(valueStr);
		} else if (clazz == double.class || clazz == Double.class) {
			return (T) Double.valueOf(valueStr);
		} else if (clazz == char.class || clazz == Character.class) {
			if (valueStr.length() > 1) {
				throw new IllegalArgumentException("参数[" + valueStr + "]" +
						"中含有多个字符，无法解析为" + className + "类型。");
			}
			return (T) Character.valueOf(valueStr.charAt(0));
		} else if (clazz == boolean.class || clazz == Boolean.class) {
			return (T) Boolean.valueOf(valueStr);
		} else {
			throw new IllegalArgumentException("不支持解析此类型：" + className);
		}
	}

}